package cc.verywell.pureblock.net;
/**
 * 
 * 哈，这个好玩了，密码这东西要有时间，或者用现有的轮子。
 * 可是现在的轮子都太复杂了，我整个简单的，有空再改正;
 * 
 * 1.client: 建个临时RSA，把RSA公钥扔过来；报文：2位公钥长度+RSA公钥
 * 2.server: 生成AESKey, 然后用公钥加密【AESKey】扔过去; 报文：2位密文长度+密文[2位AESKey长度+AESKey+2位Hash长度+Hash(RSA公钥+AESKey)]
 * -----------此时AES确认----------以后内容均通过AES加密-------
 * 3.client: 把client的身份证明扔过来；
 * 4.server：把server的身份证明扔过去;
* 
* Copyright 2019 TanYaqiu
* @author TanYaqiu 
* @address flat 601
* @date 2019年3月9日 下午2:43:18
*/

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.util.EncryptUtil;
import cc.verywell.pureblock.util.SignServer;
import cc.verywell.pureblock.util.sign.Aes128;
import cc.verywell.pureblock.util.sign.RsaServer;

public class HandshakeChannelClient extends AbstractChannel{
	final static private int LENGTH_SIZE = 2;
	final static private String HASH_NAME = "SHA-256";

	public HandshakeChannelClient(Object own) {
		super(own);
		try {
			hashAccumulate = MessageDigest.getInstance(HASH_NAME);
		} catch (NoSuchAlgorithmException e) {
			if(Pblog.INIT)Pblog.INIT(e);
			throw new RuntimeException(e);
		}
	}
	
	final private MessageDigest hashAccumulate; ///用作比较,保证握手包未篡改;
	final private ByteBuffer buffer = ByteBuffer.allocate(65536+LENGTH_SIZE);
	private int timeOutSecond;
	private HandshakeFunction<HandshakeChannelClient> callback;
	
	/**
	 * 就是reset ， Handshake必须能复用
	 * @param channel
	 * @param timeOutSecond
	 * @param callback
	 */
	public void init(AsynchronousSocketChannel channel,int timeOutSecond,HandshakeFunction<HandshakeChannelClient> callback) {
		this.channel = channel;
		this.timeOutSecond = AioProvider.TIME_OUT;
		this.callback = callback;
		
		if(Pblog.DEBUG)Pblog.DEBUG(this,"@init");
		startStep0();
	}
	public HandshakeChannelClient clearUser() {
		this.channel = null;
		this.timeOutSecond = 10;
		this.callback = null;
		
		hashAccumulate.reset();
		buffer.clear();
		lastSize = 0;
		
		this.ss = null;
		return this;
	}
	
	public SignServer ss;
	
	//此时正处于TCP第三次握手，由Client先发信息;
	private void startStep0() {
		if(Pblog.DEBUG)Pblog.DEBUG("step0-RsaInit.. ",this);
		byte[] pubkey;
		ss = new RsaServer();
		pubkey = ss.getKey().getEncoded();
		hashAccumulate.update(pubkey);
		if(Pblog.DEBUG)Pblog.DEBUG("step0-RsaReady ",this);
		
		buffer.clear();
		buffer.putShort((short)pubkey.length);
		buffer.put(pubkey);
		buffer.flip();
		
		toWriteBuffer();
//		if(Pblog.DEBUG)Pblog.DEBUG("step0-Finish buf.remain:",buffer.remaining(),this);
	}
	private void finishStep1(){
		buffer.clear();
		if(Pblog.DEBUG)Pblog.DEBUG("step1 ",this);
		startRead();
	}
	
	//TODO unread ByteBuffer 未处理;
	private void finishStep2(byte[] aesKey,byte[] hash,ByteBuffer unread) throws IOException{
		hashAccumulate.update(aesKey);
		if(!Arrays.equals(hash, hashAccumulate.digest())){
			throw new IOException("handshake hash wrong");
		}
		
		EncryptUtil eu = new Aes128(aesKey);
		int salt = HandshakeFunction.getSalt(hash);
		
//		CompletionHandler<EncryptUtil, AsynchronousSocketChannel> callback = this.callback;
//		AsynchronousSocketChannel channel = this.channel;
//		clearUser();
		if(Pblog.DEBUG)Pblog.DEBUG("step2 ",this);
		callback.handshakeCompleted(this, eu, unread, salt);
	}
	
	
	/**
	 * 写控件
	 * 
	 * 
	 */
	private void toWriteBuffer() {
		channel.write(buffer,timeOutSecond,TimeUnit.SECONDS, this, WriteHandler);
	}
	protected void wrote(int result) {
//		if(Pblog.DEBUG)Pblog.DEBUG("wrote ",result," remaining:",buffer.remaining()," ",this);

//		if(Pblog.DEBUG)Pblog.DEBUG("wrote ",result," @",this);
		if(buffer.hasRemaining()) {
			toWriteBuffer();
			return;
		}
		finishStep1();
	}
	protected void wroteFailed(Throwable exc) {
		//有错误，不执行解锁，直接关闭连接 readLock.release(); 
		if(Pblog.IO)Pblog.IO(this,"@writ: ",exc);
		close(exc);
	}

	
	/**
	 * 读控件 , 需要timeOut, 超时就关闭;
	 * 
	 * startRead()->ReadHandler->readed()
	 * 
	 */
	private int lastSize;
	public void startRead() {
		channel.read(buffer,timeOutSecond,TimeUnit.SECONDS,this,ReadHandler);
	}
	protected void read(int result) {
//		if(Pblog.DEBUG)Pblog.DEBUG("read ",result," @",this);
		if(result<=0) {//对方通知关闭，直接关闭连接; 
			if(Pblog.IO)Pblog.IO(this,"@read: read closed.");
			close(new IOException("remote request close."));
			return;
		}
		buffer.flip();
		decode:while(buffer.hasRemaining()) {
			try {
				if(lastSize==0) {
					if(buffer.remaining()<LENGTH_SIZE)break decode;
					lastSize = 0xFFFF & buffer.getShort();
					if(lastSize<=0) {
						throw new IOException("read size :"+lastSize+"<=0");
					}
				}
				if(lastSize>buffer.remaining()) {
					if(lastSize>buffer.capacity()) {
						//可加入自动扩大类 ，或在协议处分包
						throw new IOException("read size :"+lastSize+" > buffer capacity:"+buffer.capacity());
					}
					break decode;
				}
				int size = lastSize;
				lastSize = 0;
				int limit = buffer.limit();
				int position = buffer.position()+size;
				buffer.limit(position);
				ss.decode(buffer);
				
				size = 0xFFFF & buffer.getShort();
				byte[] aesKey = new byte[size];
				buffer.get(aesKey);
				size = 0xFFFF & buffer.getShort();
				byte[] hash = new byte[size];
				buffer.get(hash);

				buffer.limit(limit);
				buffer.position(position);
				finishStep2(aesKey,hash,buffer);
				return;
			}catch(Throwable exc) {//解压包有有错误，不执行解锁，直接关闭连接 ; 
				if(Pblog.IO)Pblog.IO(this,"@deco: ",exc);
				close(exc);
				return;
			}

		}
		buffer.compact();
		startRead();
	}
	protected void readFailed(Throwable exc) {
		if(Pblog.IO)Pblog.IO(this,"@read: ",exc);
		close(exc);
	}
	
	/**
	 * 关闭控件:
	 * 
	 * 
	 */
	@Override
	public void close() {
		close(null);
	}
	
	public void close(Throwable exp) {
		super.close();
//		this.channel=null;
		if(Pblog.IO)Pblog.IO(this,"@clsd: closed.");
		
//		CompletionHandler<EncryptUtil, AsynchronousSocketChannel> callback = this.callback;
//		AsynchronousSocketChannel channel = this.channel;
//		clearUser();
		callback.handshakeFailed(this, exp);
	}

	
	@Override
	public boolean isClient() {
		return true;
	}
	@Override
	public String toString() {
		try {
			InetSocketAddress address = (InetSocketAddress)this.getChannel().getLocalAddress();
			int port = address.getPort();
			StringBuffer sb = new StringBuffer();
			sb.append("connect-").append(port);
			super.appendNoSessionToString(sb);
			return sb.toString();
		}catch(Exception exp) {
			StringBuffer sb = new StringBuffer();
			sb.append(getClass().getSimpleName()).append(":").append(exp);
			super.appendNoSessionToString(sb);
			return sb.toString();		}
	}

	
}
